/*
 * Created on Aug 25, 2004
 *
 */
package com.stottlerhenke.simbionic.engine.parser;

import com.stottlerhenke.simbionic.engine.SB_SingletonBook;
import com.stottlerhenke.simbionic.engine.file.SB_StreamReader;
import com.stottlerhenke.simbionic.api.*;
import com.stottlerhenke.simbionic.common.*;
import java.util.ArrayList;
import java.lang.reflect.*;


/**
 * 
 * User defined class variables that correspond to instances of actual Java classes
 */
public class SB_VarClass extends SB_Variable
{
  /**
   * This constructor is only for creating objects to be specified with
   * setClassObject
   * 
   * @see setClassObject
   *
   */
  public SB_VarClass()
  {
  }
  
	/**
	 * Creates a new SB_VarClass with a particular id. 
	 * The class instance is not actually created until load is called.
	 * 
	 */
	public SB_VarClass(int classId)
	{
		super();
		
		_classId = classId;
		
		_parameterValue = new SB_Param();
		_parameterValue.setType(SB_ParamType.kSB_Data);
	}

	public SB_VarClass(SB_VarClass other) throws SB_Exception
	{
		_classId = other._classId;
		_class = other.getClassClass();
		_parameterValue = other.getSB_Param().cloneParam();
		_parameterValue.setType(SB_ParamType.kSB_Data);
	}
	
	/**
	 * Creates a new SB_VarClass for a static class name.
	 * 
	 * @param className fully qualified class name
	 * @throws SB_Exception
	 */
	public SB_VarClass(String className) throws SB_Exception
	{
		try
		{
			if( className == null )
				throw new SB_Exception("Can't create a static class reference from a null class name", EErrCode.kE_FORMAT);	
	
			
			_class = Class.forName(className);
			_parameterValue = new SB_Param();
			_parameterValue.setData(null);				
			_classId = SB_VarType.kClass.getState();
		}
		catch(Exception ex)
		{
			throw new SB_Exception("Unable to find fully qualified java class name for: " + className + " due to " + ex.getMessage(), EErrCode.kE_FORMAT);		
		}
	}
	
	public SB_Variable Clone() throws SB_Exception
	{
    return new SB_VarClass(this);
	}

	/**
	 *  Setup a new var class based on an object. Technically, this should
	 * be a constructor, but creating a constructor with object can override other
	 * constructors.
	 * 
	 * @param obj
	 */
	public void setClassObject(Object obj)
	{
	  _classId = SB_VarType.kClass.getState();
	  _class = obj.getClass();
	  _parameterValue = new SB_Param(obj);
	}
	
	/**
	 * 
	 * @return the object held in this class, null if static class reference
	 */
	public Object getClassObject()
	{
	  return _parameterValue.getData();
	}
	
	/**
	 * 
	 * @return the java class information for the type of object held in this class
	 */
	public Class getClassClass()
	{
	  return _class;
	}
	
	public void setSB_Param(SB_Param newParam) throws SB_Exception
  { 
    _parameterValue = newParam;
    
    Object data = _parameterValue.getData();
    if(data != null)
    {
        _class = data.getClass();
    }
  }
	
	public SB_VarType GetType() 
	{ 
		return SB_VarType.kClass;
	}
	
	public SB_Variable equals( SB_Variable arg2) throws SB_Exception  
	{
    SB_Variable result = null;

    if(arg2.GetType().getState() >= SB_VarType.kCLASS_START.getState())
    {
      Object obj1 =_parameterValue.getData();
      Object obj2 = ((SB_VarClass) arg2)._parameterValue.getData();
      result = new SB_VarBoolean( obj1.equals(obj2) );
    }
    
    return result;

  }
	/**
	 * Creates a class instance for this SB_VarClass
	 */	
	protected void load(SB_StreamReader reader, SB_SingletonBook book)
			throws SB_Exception
	{	
		String className = new String();
		try
		{
			className = (String) book.getUserClassMap().getJavaClassName(_classId);
			
			if( className == null )
				throw new SB_Exception("Class Id not associated with any class name : " + _classId,
	          EErrCode.kE_FORMAT);	
			
			_class = Class.forName(className);
			_parameterValue = new SB_Param();
			_parameterValue.setData(null);	
		}
		catch(ClassNotFoundException ex)
		{
		    throw new SB_Exception("Unable to find a Java class that matches the given class name: " + className, ex);	
		}
	}
	
	/**
	 * Create an class instance
	 * 
	 * @param book
	 * @param name
	 * @param parameters
	 * @return
	 * @throws SB_Exception
	 */
	static public SB_VarClass createInstance(SB_SingletonBook book, String name, ArrayList params) throws SB_Exception
	{
	  if(params == null)
	    throw new SB_Exception("Unable to create class instance of : " + name + "due to: null parameter value",
          EErrCode.kE_ERROR);		
	    
		String className = new String();
		SB_VarClass returnValue = new SB_VarClass();
		try
		{
			className = (String) book.getUserClassMap().getJavaClassName(name);
			
			if( className == null )
				throw new SB_Exception("Class name not associated with any class package : " + name,
	          EErrCode.kE_ERROR);	
			
			Class c = Class.forName(className);
			
			if( SIM_Constants.DEBUG_INFO_ON && c == null)
			  book.getLogger().log("Couldn't find class for: " + className, SB_Logger.ERROR);
			
			Object o = null;
			
      Constructor[] theConstructors = c.getConstructors();
      
			if( SIM_Constants.DEBUG_INFO_ON && theConstructors == null)
			  book.getLogger().log("Couldn't find constructors for: " + className, SB_Logger.ERROR);
			
      for (int i = 0; i < theConstructors.length; i++) 
      {
  			if( SIM_Constants.DEBUG_INFO_ON && theConstructors[i] == null)
  			  book.getLogger().log("Null constructor at index: " + i, SB_Logger.ERROR);

        Class[] parameterTypes = theConstructors[i].getParameterTypes();
      
  			if( SIM_Constants.DEBUG_INFO_ON && parameterTypes == null)
  			  book.getLogger().log("Null parameters for constructor at index: " + i, SB_Logger.ERROR);

        if(CompareParameters(params, parameterTypes))
        {
          o = theConstructors[i].newInstance(params.toArray());
          break;
        }
      }
			
			returnValue.setClassObject( o );
		}
		catch(Exception ex)
		{
			throw new SB_Exception("Unable to create class instance of : " + className + "due to: " + ex.toString(),
          EErrCode.kE_ERROR);		
		}
		
		return returnValue;
	}
	
  /**
   * 
   * @param params array of objects
   * @param pvec array of class objects
   * @return true if the two parameters arrays are of the same type
   */
  static public boolean CompareParameters(ArrayList params, Class[] pvec)
  { 
  	boolean bReturn = true;
  	
  	int count = params.size();
  	if( pvec.length == count ) //at least they are the same size
  	{
  		for( int x = 0; x < count; x++)
  		{
  			Class classOne = pvec[x]; //What the function expects
  			
  			if( classOne.isPrimitive())
  			{
  				Object obj = params.get(x); //What the user has given
  				
  				if( obj instanceof Boolean && classOne.equals(Boolean.TYPE))
  					bReturn = true;
  				else
  				if( obj instanceof Character && classOne.equals(Character.TYPE))
  					bReturn = true;
  				else
    				if( obj instanceof Byte && classOne.equals(Byte.TYPE))
  					bReturn = true;
    			else
      		if( obj instanceof Short && classOne.equals(Short.TYPE))
  					bReturn = true;
      		else
        	if( obj instanceof Integer && classOne.equals(Integer.TYPE))
  					bReturn = true;
        	else
          if( obj instanceof Long && classOne.equals(Long.TYPE))
  					bReturn = true;
  				else
    			if( obj instanceof Float && classOne.equals(Float.TYPE))
  					bReturn = true;
  				else
  				if( obj instanceof Double && classOne.equals(Double.TYPE))
  					bReturn = true;
  				else
  				{
  					bReturn = false;
  					break;
  				}
  			}
  			else
  			if( !classOne.isInstance(params.get(x)) ) //According to doc, doesn't work with primitives
  			{
  					bReturn = false;
  					break;
  			}
  		}
  	}
  	else
  		bReturn = false;
  	
  	return bReturn;
  }
  
	public String toString()
	{
	  if(_class != null)
	   return new String("kClass: " + _class.getName());
	  else
	    return new String("kClass: null");
	 }
	
	protected int _classId;
	protected Class _class; //The class of the object held herein
}
